【C++之容器篇】造轮子:string类的模拟实现 您所在的位置:网站首页 stringappend 方法 【C++之容器篇】造轮子:string类的模拟实现

【C++之容器篇】造轮子:string类的模拟实现

#【C++之容器篇】造轮子:string类的模拟实现| 来源: 网络整理| 查看: 265

目录 前言一、项目结构二、string中的基本结构:成员变量三、默认成员函数1. 构造函数(1)普通构造函数:参数为一个字符串(2)无参构造函数(3)全缺省参数版本的默认构造函数 2. 析构函数3. 拷贝构造函数(1)浅拷贝(2)深拷贝 4. 赋值运算符重载(1)浅拷贝(2)深拷贝 四、容量接口1. size:返回string对象中存储的有效数据个数2. length:返回string对象中存储的字符串的长度3. capacity:返回string对象当前有效容量4. reserve:扩容5. resize:扩容+初始化 五、元素访问接口1. operator[](1) char& operator[](size_t pos)(2)const char& operator[](size_t pos) 2. at()(1) char& at(size_t pos)(2)const char& at(size_t pos) const 六、迭代器1. 普通对象的迭代器2. const对象的迭代器3.一个类支持了迭代器就支持范围for 七、修改接口1. operator+=()(1) hjt::string& operator+=(char c)(2)hjt::string& operator+=(const char* s)(3)hjt::string& operator+=(const hjt::string& str)(4)测试+= 2. append()(1) hjt::string& append(char c)(2)hjt::string& append(const char* s)(3)hjt::string& append(const hjt::string& str) 3. push_back()4. pop_back()5. insert()(1) string& insert(size_t pos,char ch)(2)string& insert(size_t pos,const char* s) 6. string& erase(size_t pos,size_t len = npos) 八、操作函数接口1. c_str():返回string对象中的字符指针2. find()(1)size_t find(char c,size_t pos = 0)(2)size_t find(const char* s,size_t pos = 0) 3. rfind()(1)size_t rfind(char c,size_t pos = npos) 九、非成员函数接口1.operator()3. string对象的比较函数(1). operator=()(6). operator!=()(7). 简单的测试比较函数代码: 7. getline()

前言

前面我们学习了string类型中常见函数接口的使用,今天这篇文章我们重点是要对string做到知根知底,这样我们才知道更加熟练地使用string来处理各种问题。

在这里插入图片描述

一、项目结构

在这里插入图片描述 在模拟实现string的时候,我们需要创建一个项目,这个项目中包含一个test.cpp的文件和一个string.h的头文件。test.cpp文件主要是测试string的逻辑,string.h文件主要是声明和定义string中的代码。

二、string中的基本结构:成员变量 namespace hjt { class string { private: char* _str; size_t _size; size_t _capacity; }; }

分析:需要注意的是,我们在模拟实现自己的string的时候需要包含在我们自己的命名空间中,这样可以防止和标准库中的string发生冲突。一个string类中包含的成员变量主要是:_str,_size,_capacity。

_str:是char*类型的指针,主要是指向一块动态开辟的空间,空间中存放的是字符串_size:是size_t类型的整数,主要表示string中存放的有效字符个数。_capacity:是size_t类型的整数,主要表示string中当前的容量,以便定时知道_str指向的数组是否满了,如果满了则需要进行扩容。 三、默认成员函数 1. 构造函数 (1)普通构造函数:参数为一个字符串 代码: // 构造函数 string(const char* s) :_size(strlen(s)) ,_capacity(_size) { // 开空间 _str = new char[_capacity + 1]; strcpy(_str, s); } // 测试代码 int main() { hjt::string s("hello hjt::string"); return 0; }

调试结果: 在这里插入图片描述

注意:

错误的做法: 在构造函数的实现中,一定不能单纯将s的值赋给_str,首先s是const char类型的,_str是char类型的,权限会放大,不支持赋值。其次,s和_str都是字符指针,如果将s直接赋值给_str,那么结果就是使得s和_str指向了同一块空间,这个空间中存放的是常量字符串,是不允许修改的,_str指向这块空间,所以_str也就是不能修改的,这显然是不合理。正确的做法:使用const char* s计算处其字符个数,然后根据这个字符个数去申请一块新的空间,在申请的时候一定需要注意比实际存放的有效字符多一个空间以存储’\0’。然后再使用strcpy函数将s的字符串内容拷贝到这块空间即可。 (2)无参构造函数 代码: // 无参构造函数 string() :_size(0) ,_capacity(0) { _str = new char[1]; _str[0] = '\0'; }

调试结果: 在这里插入图片描述

注意:

错误做法: 实现无参构造函数的时候,一定要注意其中的_str不能设置成空指针,因为在外面访问的时候是有可能访问一个空的string对象的,如果设置成空指针,那么外面在访问空的string对象的时候就会访问到空指针从而导致程序崩溃。正确做法: 对于空的string对象,可以考虑开一个字符的空间存储’\0’,表示该string对象中存放的是空字符串。 (3)全缺省参数版本的默认构造函数 代码: // 全缺省参数版本的默认构造函数 string(const char* s = "") :_size(strlen(s)) , _capacity(_size) { // 开空间 _str = new char[_capacity + 1]; strcpy(_str, s); }

调试结果: 在这里插入图片描述

分析:可以考虑给予const char* s一个缺省值,给成一个空字符串,当没有传参的时候,s为空字符串,通过strlen(s)进行计算的结果是0,所以_size和_capacity也都是0,最终开的空间大小就是_capacity+0 = 0+1 = 1,然后将s的内容拷贝过去的时候就会将s中的’\0’拷贝到_str指向的空间,效果和无参构造函数一模一样。

2. 析构函数

string类型中有动态申请的资源,所以需要我们自己手动实现析构函数完成对象资源的清理。

~string() { if (_str) { delete[]_str; _size = _capacity = 0; } } 3. 拷贝构造函数 (1)浅拷贝

我们知道,拷贝构造函数是类的默认成员函数,所以当我们没有显示实现的时候,编译器会自动帮我们生成一个浅拷贝。现在我们来测试以下编译器生成的浅拷贝构造函数做了哪些事情还要造成什么后果。

代码:

调试结果: 现象1: 在这里插入图片描述 现象2: 在这里插入图片描述 在这里插入图片描述

分析: 浅拷贝的过程就是直接将被拷贝的string对象中的_str的值直接赋值给新创建对象的_str,这样就会导致被拷贝对象中的_str指针和新创建的string对象的_str指针指向同一块空间,那么在对象生命周期结束时调用析构函数清理资源的时候就会发现两个对象都需要调用析构函数完成对应资源的清理,那么这块空间就会被析构两次,从而使程序崩溃。浅拷贝还有另一个问题:**因为两个对象中的指针都指向同一块空间,所以对其中一个对象数据的修改显然会影响另一个对象中的数据,这显然是不合理。**所以:对于string类型,必须手动实现深拷贝。

(2)深拷贝 代码: // 拷贝构造函数:深拷贝 string(const hjt::string& str) :_size(str._size) ,_capacity(str._capacity) { _str = new char[_capacity + 1]; strcpy(_str, str._str); } 调试结果: 在这里插入图片描述

分析:对于深拷贝,两个对象中的指针显然不再是指向同一块空间,所以不存在同一块空间被释放两次,也不存在修改一个对象的数据会影响另一个对象的数据。 深拷贝的实现逻辑:利用被拷贝的对象计算出新对象的空间大小,然后向系统申请对应的空间,注意同样需要多开一个空间存储’\0’,然后再使用strcpy()函数将被拷贝对象中的数据拷贝到新对象的空间。

4. 赋值运算符重载

赋值运算符重载和拷贝构造很类似,同样存在深拷贝和浅拷贝的问题。

(1)浅拷贝

我们知道赋值运算符重载函数同样是类的默认成员函数,所以我们不显示实现编译器会默认生成一个赋值运算符重载函数,现在我们来测试以下编译器生成的浅拷贝构造函数做了哪些事情还要造成什么后果。

代码: int main() { hjt::string s1("hello hjt::string::operator=(const hjt::string& str);"); hjt::string s2; s2 = s1;// 两个已经存在对象的赋值:调用赋值运算符重载函数 return 0; }

调试结果: 现象1: 在这里插入图片描述 现象2: 在这里插入图片描述 在这里插入图片描述

分析: 浅拷贝的过程就是直接将被拷贝的string对象中的_str的值直接赋值给新创建对象的_str,这样就会导致被拷贝对象中的_str指针和新创建的string对象的_str指针指向同一块空间,那么在对象生命周期结束时调用析构函数清理资源的时候就会发现两个对象都需要调用析构函数完成对应资源的清理,那么这块空间就会被析构两次,从而使程序崩溃。浅拷贝还有另一个问题:**因为两个对象中的指针都指向同一块空间,所以对其中一个对象数据的修改显然会影响另一个对象中的数据,这显然是不合理。**所以:对于string类型,必须手动实现深拷贝。

(2)深拷贝 代码: int main() { hjt::string s1("hello hjt::string::operator=(const hjt::string& str);"); hjt::string s2; s2 = s1;// 两个已经存在对象的赋值:调用赋值运算符重载函数 return 0; }

调试结果: 在这里插入图片描述

分析:对于赋值运算符重载函数,需要实现深拷贝,两个对象中的指针显然不再是指向同一块空间,所以不存在同一块空间被释放两次,也不存在修改一个对象的数据会影响另一个对象的数据。 赋值运算符重载函数深拷贝的实现逻辑:判断两个对象的地址是否是一样的,如果一样,则为自己给自己赋值,不需要进行任何操作,直接返回即可。如果地址不一样,则先利用被拷贝的对象得出新空间大小,然后向系统申请对应的空间,注意同样需要多开一个空间存储’\0’,然后再使用strcpy()函数将被拷贝对象中的数据拷贝到新的空间,接着释放原来对象的空间,让其指针指向这块新空间,更新原来对象的_size和_capacity。

四、容量接口 1. size:返回string对象中存储的有效数据个数 代码: // size() size_t size() const { return _size; } // 测试代码 int main() { hjt::string s("hello hjt::string::size()"); cout hjt::string s("hello hjt::string::length()"); cout hjt::string s("hello hjt::string::capacity()"); cout char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[]_str; _str = tmp; _capacity = n; } } // 测试代码 int main() { hjt::string s("hello hjt::string::reserve()"); cout if (n if (n > _capacity) { reserve(n); } for (int i = _size; i hjt::string s("hello resize(size_t n,char c)"); cout hjt::string s("hello resize(size_t n,char c)"); s.reserve(50); cout _str[i] = c; } _size = n; _str[_size] = '\0'; }

实现逻辑:我们只需要检查是否需要进行扩容,如果需要则进行扩容,接下来就是初始化数据,初始化到下标为n的地方,再将最后的位置置成’\0’。如果刚开始n hjt::string s("hello char& operator[](size_t pos)"); for (int i = 0; i assert(pos cout hjt::string s("hello hjt::string::at(pos)"); for (int i = 0; i assert(pos return _str + _size; }

使用代码:

int main() { hjt::string s("hello iterator"); hjt::string::iterator it = s.begin(); while (it != s.end()) { cout return _str + _size; }

使用代码:

// 测试const迭代器 int main() { const hjt::string s("hello hjt::string::const_iterator"); hjt::string::const_iterator cit = s.begin(); while (cit != s.end()) { cout cout hjt::string s("hello iterator"); hjt::string::iterator it = s.begin(); while (it != s.end()) { cout append(c); return *this; } (2)hjt::string& operator+=(const char* s) hjt::string& operator+=(const char* s) { append(s); return *this; } (3)hjt::string& operator+=(const hjt::string& str) hjt::string& operator+=(const hjt::string& str) { append(str); return *this; } (4)测试+= 代码 int main() { hjt::string s("x"); cout reserve(len); } strcpy(_str + _size, s); _size = len; return *this; } int main() { hjt::string s("hello "); s.append("hjt::string::append"); cout hjt::string s1("hello "); hjt::string s2("hjt::string::append(const hjt::string& str)"); s1.append(s2); cout reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = c; _size++; _str[_size] = '\0'; } int main() { hjt::string s; s.push_back('x'); cout assert(pos _str[end] = _str[end - 1]; end--; } _str[pos] = c; _size++; return *this; } // 测试insert int main() { hjt::string s("hello"); s.insert(0, '!');// 头插 cout reserve(len + _size); } size_t end = _size + len; while (end > pos + len - 1) { // end的最后一个位置是pos+len _str[end] = _str[end - len]; end--; } strncpy(_str + pos, s, len); _size += len; return *this; } ```cpp int main() { hjt::string s("hello "); // 尾插 s.insert(s.size(), "string"); cout _size = pos; _str[_size] = '\0'; } else { // 删除len个有效字符:挪动数据 size_t begin = pos + len; while (begin hjt::string s("hello hjt::string::erase(size_t pos,size_t len = npos)"); // 删除hello s.erase(0, 5); cout hjt::string s("hello hjt::string::c_str()"); cout if (_str[i] == c) { return i; } } return npos; } // 测试find int main() { hjt::string s("hello size_t hjt::string::find(char c,size_t pos = npos)"); size_t pos = s.find('t'); if (pos != hjt::string::npos) { // 找到了 cout const char* p = strstr(_str + pos, s); if (p == nullptr) { return npos; } else { return p - _str; } } // 测试find int main() { hjt::string s("hello size_t hjt::string::find(char c,size_t pos = npos)"); size_t pos = s.find("size_t"); if (pos != hjt::string::npos) { // 找到了 cout if (pos == npos) { pos = _size; } for (int i = pos; i >= 0; i--) { if (_str[i] == c) { return i; } } return npos; } // 测试rfind int main() { hjt::string s("hello size_t hjt::string::find(char c,size_t pos = npos)"); size_t pos = s.rfind('n'); if (pos != hjt::string::npos) { // 找到了 cout for (auto& ch : str) { cout char ch; ch = in.get(); while (ch != ' ' && ch != '\n') { str += ch; ch = in.get(); } return in; } int main() { hjt::string s; cin >> s; cout '\0'}; int count = 0; while (ch != ' ' && ch != '\n') { buff[count++] = ch; if (count == 127) { str += buff; memset(buff, '\0', 128); count = 0; } ch = in.get(); } str += buff; return in; } 代码3: istream& operator>>(istream& in, hjt::string& str) { char ch; ch = in.get(); str.reserve(128); while (ch != ' ' && ch != '\n') { str += ch; ch = in.get(); } return in; } 3. string对象的比较函数 (1). operator return strcmp(s1.c_str(), s2.c_str()) == 0; } (3). operator return !(s1=() 代码: bool operator>=(const hjt::string& s1, const hjt::string& s2) { return !(s1 hjt::string s1("hello"); hjt::string s2("string"); hjt::string s3(s2); cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有